import datetime
import gzip
import json
import time
from io import BytesIO

import numpy as np
import pandas as pd
import requests
from loguru import logger
from topsdk.client import TopApiClient, TopException
from topsdk.defaultability.defaultability import Defaultability
from topsdk.defaultability.request.alitrip_policy_special_compression_upload_request import \
    AlitripPolicySpecialCompressionUploadRequest
from topsdk.defaultability.request.alitrip_policy_special_upload_request import *
from tqdm import tqdm


def log_config():
    # 自定义日志格式
    # logger.remove()
    # logger_format = "{time} | {level} | {thread.name} | {file}:{function}:{line} - {message}"

    # 添加输出处理程序，将日志输出到文件
    logger.add("./logs/info_file.log", level="INFO")
    logger.add("./logs/error_file.log", level="ERROR")


def send_fzapi_request(request):
    """
    发送请求
    :return:
    """
    # create Client
    client = TopApiClient(appkey='25346003', app_sercet='aeda58e87f78ea981a141ac507e5a940',
                          top_gateway_url='http://39.98.61.147/huifei/',
                          verify_ssl=False)
    ability = Defaultability(client=client)

    # 将 policy_request 转换为 JSON 字符串
    # json_data = json.dumps(request.to_dict())
    logger.info("【飞猪特殊政策上传请求】: 请求中... ...")

    """
    请求接口
    """
    max_tries = 20  # 最大尝试次数
    num_tries = 0  # 已尝试次数
    sleep_time2 = 5  # 设置等待时间
    while num_tries < max_tries:
        try:
            response = ability.alitrip_policy_special_upload(request,
                                                             '6102a098713e7108013e55b21b78e46c0f649d29cd2fedf4259705970')
            logger.info(f"【飞猪特殊政策上传响应】: {response}")
            flag1 = response.get('result', {}).get('data', {}).get('create_result', {}).get('success') and response.get(
                'result', {}).get('data', {}).get('create_result', {}).get('error_code', '') != '003'
            flag2 = response.get('result', {}).get('data', {}).get('delete_result', {}).get('success')
            if not (flag1 or flag2):
                num_tries += 1
                if num_tries >= max_tries:
                    # 如果已经尝试到最大次数，跳出循环，放弃重试
                    logger.error(f"重试{max_tries}次后仍然失败，放弃重试。")
                    break
                logger.warning(f"请求失败，休眠 {sleep_time2} 秒后进行第 {num_tries + 1} 次尝试。")
                time.sleep(sleep_time2)
                continue
            logger.info("请求成功")
            return  # 成功请求到数据，直接返回
        except (TopException, requests.exceptions.RequestException) as e:
            logger.error(e)
            num_tries += 1
            if num_tries >= max_tries:
                # 如果已经尝试到最大次数，跳出循环，放弃重试
                logger.error(f"重试{max_tries}次后仍然失败，放弃重试。")
                break
            # 打印日志，并且睡眠
            logger.info(f"请求失败，休眠 {sleep_time2} 秒后进行第 {num_tries + 1} 次尝试。")
            time.sleep(sleep_time2)


def send_ycapi_request(sxyr_c_n):
    """
    发送请求的函数，这里使用requests库发送请求。
    :return: pandas df对象
    """
    url = f"http://192.168.110.135:6181/api/czndc/flightcabin3"

    # 打开文件
    try:
        with open(f'./{sxyr_c_n}/request.json', 'r', encoding='utf-8') as file:
            # 读取文件内容
            data = file.read()

            # 解析 JSON 数据
            payload = json.loads(data)
    except Exception as e:
        logger.error(f"读取请求参数文件内容: 异常 {str(e)}")

    headers = {"content-type": "application/json"}

    logger.info("南航R舱数据请求中... ...")
    response = requests.request("POST", url, json=payload, headers=headers, timeout=3000)
    # logger.info(response.text)
    # 检查响应状态码是否为200
    if response.status_code == 200:
        try:
            rs_json = json.loads(response.text)
            logger.error('南航R舱数据请求失败：{}'.format(rs_json))
            raise RuntimeError("发生错误，程序停止")

        except json.decoder.JSONDecodeError:
            # 读取响应中的数据流
            data_stream = response.content

            # 使用 pd.ExcelFile 打开数据框
            rnum_excel_file = pd.ExcelFile(BytesIO(data_stream), engine='openpyxl')

            logger.info("南航R舱数请求结束。")
            return rnum_excel_file

    else:
        logger.error('文件下载失败，状态码：{}'.format(response.status_code))
        raise RuntimeError("发生错误，程序停止")


def set_excel_table(rnum_excel_file, set_p_c, set_cn, set_p_n):
    """
    处理Excel表格，去哪游全国与南航R舱数据合并
    :param set_p_n: 政策备注
    :param set_p_c: 政策代码
    :param set_cn: 渠道标识
    :param rnum_excel_file: 南航R舱数据
    :return:
    """
    # 读取 Excel
    logger.info('读取 Excel 文件... ...')
    # 定义全局文件路径配置
    file_path = f"./{set_cn}/"
    rprice_excel_file = pd.ExcelFile(rf"{file_path}R仓特惠航线.xlsx")

    # 创建一个ExcelWriter对象
    writer = pd.ExcelWriter(rf"{file_path}CZR-{set_cn}.xlsx")

    # 获取所有的sheet页名称
    sheet_names = rnum_excel_file.sheet_names
    # 遍历sheet页名称集合
    for sheet_name in sheet_names:
        logger.info(f'数据合并 {sheet_name}开始 ... ...')
        # 通过sheet名获取数据并进行相应处理
        # sheet_name = '0101-0119'
        rnum_df = rnum_excel_file.parse(sheet_name)
        rprice_df = rprice_excel_file.parse(sheet_name)

        # 根据指定的条件进行左连接，并选择要合并的列
        merged_sheet_df = pd.merge(rnum_df, rprice_df[['起飞', '到达', '起飞日期', '价格']], how='left',
                                   left_on=['起飞', '到达', '起飞日期'],
                                   right_on=['起飞', '到达', '起飞日期'])

        # 计算差价列
        # merged_sheet_df = merged_sheet_df.dropna(subset=['价格'])
        jg_flag = pd.notna(merged_sheet_df['价格'])
        merged_sheet_df['差价'] = np.where(jg_flag, merged_sheet_df['票面价'] - merged_sheet_df['价格'], np.nan)
        # fz_票面价
        merged_sheet_df['fz_票面价'] = np.where(merged_sheet_df['差价'] > 200, merged_sheet_df['价格'] + 200, merged_sheet_df['票面价'])
        # fz_竞价区间
        valid_jg_flag = pd.notna(merged_sheet_df['价格'])
        valid_diff_minus_20 = (merged_sheet_df['差价'] - 20).astype('Int64')  # 在这里进行整数类型转换
        merged_sheet_df['fz_竞价区间'] = np.where(valid_jg_flag, valid_diff_minus_20, np.nan)

        # 获取飞猪模板excel
        fz_excel_file = pd.ExcelFile(rf"{file_path}飞猪CZR-QN2.xlsx")
        fz_df = fz_excel_file.parse('结果')

        # 转飞猪报表
        cz_excel_to_fz(merged_sheet_df, set_p_c, sheet_name, fz_df, file_path, set_p_n)

        # 将合并后的结果写入Excel文件的不同sheet页中
        merged_sheet_df.to_excel(writer, sheet_name=sheet_name, index=False)

    # 保存ExcelWriter对象
    writer._save()
    writer.close()


def cz_excel_to_fz(merged_sheet_df, cetf_p_c, sheet_name, fz_df, cetf_file_path, cetf_p_n=''):
    """
    将cz的excel报表转换为飞猪的报表
    :param cetf_file_path:
    :param cetf_p_n: 政策备注
    :param cetf_p_c: 政策代码
    :param fz_df: 飞猪报表
    :param merged_sheet_df: Sheet页的DataFrame
    :param sheet_name: Sheet页名称
    :return: None
    """
    logger.info(f'========= 南航R舱报表 {sheet_name} 转换飞猪报表开始 =========')
    rpc_df = merged_sheet_df.dropna(subset=['价格']).reset_index(drop=True)

    # 获取 DataFrame 的所有列名
    column_names = fz_df.columns
    # 获取需要拓展的行数差值
    num_rows_to_add = len(rpc_df) - len(fz_df) + 10
    # 创建一个与 fz_df 匹配的新 DataFrame，并将其复制多次扩展行数
    extended_df = pd.concat([fz_df] * (num_rows_to_add // len(fz_df)) + [fz_df.iloc[:num_rows_to_add % len(fz_df), :]],
                            ignore_index=True)

    sale_type = 1
    # 遍历column列名称集合，数据转换
    for column_name in column_names:
        if column_name == '政策编码' and cetf_p_c != "None" and cetf_p_c != "":
            extended_df['政策编码'] = cetf_p_c
            continue

        if column_name == '去程允许航班号限制':
            extended_df['去程允许航班号限制'] = rpc_df['航班号'].str[2:]
            continue

        if column_name == '出发机场三字码':
            extended_df['出发机场三字码'] = rpc_df['起飞']
            continue

        if column_name == '到达机场三字码':
            extended_df['到达机场三字码'] = rpc_df['到达']
            continue

        if column_name == '去程舱位':
            extended_df['去程舱位'] = rpc_df['最低价舱']
            continue

        if column_name == '去程允许航班日期':
            extended_df['去程允许航班日期'] = rpc_df['起飞日期']
            continue

        if column_name == '销售日期':
            extended_df['销售日期'] = datetime.now().strftime(
                '%Y%m%d') + "-" + datetime.now().strftime('%Y%m%d')
            continue

        if column_name == '票面价':
            extended_df['票面价'] = rpc_df['fz_票面价']
            continue

        if column_name == '固定金额竞价空间':
            extended_df['固定金额竞价空间'] = rpc_df['fz_竞价区间']
            continue

        if column_name == '销售方式':
            extended_df['销售方式'] = sale_type
            continue

        if column_name == '政策备注':
            extended_df['政策备注'] = rpc_df['价格']
            continue

        extended_df[column_name] = fz_df[column_name][1:].values[0]

    # 删除 '差价' 列中为负数的行
    extended_df['政策备注'] = extended_df['政策备注'].apply(lambda x: str(x) + cetf_p_n)
    extended_df.loc[extended_df['固定金额竞价空间'] > 180, '固定金额竞价空间'] = 180
    extended_df = extended_df[(extended_df['固定金额竞价空间'] >= 0) & (extended_df['固定金额竞价空间'] <= 180)]

    # 删除 舱位 列中为W,S的行
    extended_df = extended_df[~extended_df['去程舱位'].isin(['W', 'S'])]

    # **************************** 复制第一次 ****************************
    # 复制 extended_df 的所有行并创建一个副本
    extended_df_copy1 = extended_df.copy()

    # 修改副本中指定列的值
    sale_type += 1
    extended_df_copy1['销售方式'] = sale_type

    # **************************** 复制第二次 ****************************
    # 复制 extended_df 的所有行并创建一个副本
    extended_df_copy2 = extended_df.copy()

    # 修改副本中指定列的值
    sale_type += 1
    extended_df_copy2['销售方式'] = sale_type

    # 将副本和原 DataFrame 进行拼接
    concatenated_df = pd.concat([extended_df, extended_df_copy1, extended_df_copy2])

    # **************************** 请求飞猪上传政策 ****************************
    # 使用 apply() 函数应用 process_row() 函数到每行数据上
    logger.info("开始上传政策...")
    policyDtoList = concatenated_df.apply(process_row, args=(cetf_p_c,), axis=1)
    size = 50000
    total = len(policyDtoList)
    # 在循环外部创建进度条
    progress_bar = tqdm(total=total, desc="上传政策进度")
    for i in range(0, total, size):
        # 取出最大450条数据，添加到结果列表
        end = i + size
        subset = policyDtoList[i:end]
        # 获取请求参数
        add_req = add_policy(subset)
        send_fzapi_request(add_req)
        # 每次循环结束时更新进度条
        progress_bar.update(end - i)
        logger.info(f"政策【{cetf_p_c}】上传成功: {end}/{total}")
        time.sleep(1)

    # 循环结束后关闭进度条
    progress_bar.close()

    # **************************** 生成飞猪政策报表 ****************************
    # 创建 ExcelWriter 对象
    writer = pd.ExcelWriter(rf"{cetf_file_path}飞猪CZR-QN{sheet_name}.xlsx", engine='xlsxwriter')
    # 将 DataFrame 写入指定的 sheet 名称
    concatenated_df.to_excel(writer, sheet_name='结果', index=False)
    # 关闭 ExcelWriter 对象
    writer._save()
    writer.close()

    logger.info(f'========= 南航R舱报表 {sheet_name} 转换飞猪报表结束 =========')


def process_row(row, pc):
    """
    定义一个函数，传入一行数据，转化为PolicyDto需要导入的政策
    :param pc: 政策代码
    :param row:飞猪模板数据
    :return:飞猪接口需要的PolicyDto数据
    """

    """
    行程信息
    """
    flightDTO = AlitripPolicySpecialUploadFlightDTO()
    # 允许航班号
    flightDTO.allow_flight_num = str(row['去程允许航班号限制'])
    # 去程舱位
    flightDTO.cabin = row['去程舱位']
    # 去程班期限制
    flightDTO.day_week = [x for x in str(row['去程班期限制'])]
    # 行程类型标记：0，单程；1，往返
    flightDTO.flight_index = int(row['行程类型'])
    """
    允许航班日期
    """
    allowTravelDate = AlitripPolicySpecialUploadDatePairDto()
    # 根据"-"进行分割字符串
    date_parts = row['去程允许航班日期'].split("-")
    allowTravelDate.start = datetime.strptime(date_parts[0], "%Y%m%d").replace(hour=00, minute=00, second=00).strftime('%Y-%m-%d %H:%M:%S')
    allowTravelDate.end = datetime.strptime(date_parts[1], "%Y%m%d").replace(hour=23, minute=59, second=59).strftime('%Y-%m-%d %H:%M:%S')
    flightDTO.allow_travel_date = [allowTravelDate.__dict__]

    """
    竞价信息
    """
    priceDto = AlitripPolicySpecialUploadPriceDto()
    # 代理费率
    priceDto.commission = int(row['代理费率'])
    # 留钱
    priceDto.return_price = int(row['留钱']) * 100
    # 儿童价计算方法
    priceDto.child_sale_type = int(row['儿童价计算方法'])
    # 免费托运行李额
    priceDto.baggage = int(row['免费托运行李额'])
    # 固定金额竞价空间
    priceDto.bidd_fee = int(row['固定金额竞价空间']) * 100

    """
    销售信息
    """
    saleDto = AlitripPolicySpecialUploadSaleDto()
    # 提前预定天数 只能输入A-B的格式，并且A、B都是0到365之间的整数
    saleDto.advance_day = row['提前预定天数']
    # 销售方式 0 无， 1 打包销售套餐1， 2 打包销售套餐2， 3 打包销售套餐3， 4 返现-航司运价，5 返现-销售方包装
    saleDto.sale_mode_code = int(row['销售方式'])
    """
    销售日期
    """
    saleDate = AlitripPolicySpecialUploadDatePairDto()
    # 根据"-"进行分割字符串
    date_parts = row['销售日期'].split("-")
    saleDate.start = datetime.strptime(date_parts[0], "%Y%m%d").replace(hour=00, minute=00, second=00).strftime('%Y-%m-%d %H:%M:%S')
    saleDate.end = datetime.now().replace(month=12, day=31, hour=23, minute=59, second=59).strftime('%Y-%m-%d %H:%M:%S')
    saleDto.sale_date = [saleDate.__dict__]

    """
    乘机人信息
    """
    passengerDto = AlitripPolicySpecialUploadPassengerDto()
    # 产品类型
    passengerDto.product_code = int(row['产品类型'])

    """
    需要导入的政策列表
    """
    policyDto = AlitripPolicySpecialUploadPolicyDto()
    # 政策id
    if pc is None or pc == "":
        # policy_code 为 None 或者空字符串的情况
        policyDto.policy_code = row['政策编码']
    else:
        # policy_code 不是 None 且不是空字符串的情况
        policyDto.policy_code = pc
    # 政策状态：1，有效；2，挂起；0，删除 3，停用
    policyDto.status = 1
    # policyDto.status = 2
    # 航司代码
    policyDto.airline = row['航空公司二字代码']
    # 到达机场三字码
    policyDto.arr_airport = row['到达机场三字码']
    # 是否订位
    policyDto.create_pnr = int(row['是否订位'])
    # 出发机场三字码
    policyDto.dep_airport = row['出发机场三字码']
    # 商家配置号
    policyDto.office_no = row['商家配置号']
    # 是否需要pata验价
    policyDto.pata = int(row['是否需要pata验价'])
    # 是否支持代码共享航班
    policyDto.code_share = int(row['是否支持代码共享航班'])
    # 票面价
    policyDto.fare_price = int(row['票面价'])
    # 政策备注
    policyDto.memo = str(row['政策备注'])
    # 直达中转
    policyDto.direct_transfer_type = int(row['直达中转'])
    policyDto.passenger = passengerDto.__dict__
    policyDto.sale = saleDto.__dict__
    policyDto.price = priceDto.__dict__
    policyDto.flights = [flightDTO.__dict__]
    return policyDto.__dict__


def add_policy(pds):
    """
    普通政策上传参数
    :param pds:
    :return:
    """
    policyCreateRequestDto = AlitripPolicySpecialUploadPolicyCreateRequestDto()
    # 行程类型
    policyCreateRequestDto.trip_type = 0
    # 店铺id
    policyCreateRequestDto.agent_id = 5252
    # 操作类型：ADD，增量；FULL，全量
    policyCreateRequestDto.exec_type = 'ADD'
    # 政策类型：P，普通政策；T，特殊政策；G，规则政策；
    policyCreateRequestDto.policy_type = 'T'
    # 商家传入时间
    policyCreateRequestDto.agent_send_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    # 需要导入的政策列表
    policyCreateRequestDto.policy_list = pds.tolist()
    policyCreateRequestDto.delete_policy = []

    """
    将参数转为二进制文件的数据
    """
    request = AlitripPolicySpecialCompressionUploadRequest()
    request_json = json.dumps(policyCreateRequestDto.__dict__)
    compress = gzip.compress(request_json.encode('utf-8'))
    request.file = compress
    return request


def del_policy(pc):
    """
    删除政策
    """
    deletePolicy = AlitripPolicySpecialUploadPolicyQueryParamDTO()
    deletePolicy.policy_code = pc

    policyCreateRequestDto = AlitripPolicySpecialUploadPolicyCreateRequestDto()
    # 行程类型
    policyCreateRequestDto.trip_type = 0
    # 店铺id
    policyCreateRequestDto.agent_id = 5252
    # 操作类型：ADD，增量；FULL，全量
    policyCreateRequestDto.exec_type = 'ADD'
    # 政策类型：P，普通政策；T，特殊政策；G，规则政策；
    policyCreateRequestDto.policy_type = 'T'
    # 商家传入时间
    policyCreateRequestDto.agent_send_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    # # 删除政策查询参数，需要删除时execType必须为ADD
    policyCreateRequestDto.delete_policy = [deletePolicy.__dict__]
    policyCreateRequestDto.policy_list = []

    """
    将参数转为二进制文件的数据
    """
    request = AlitripPolicySpecialCompressionUploadRequest()
    request_json = json.dumps(policyCreateRequestDto.__dict__)
    compress = gzip.compress(request_json.encode('utf-8'))
    request.file = compress
    return request


def run_code(rc_p_c, rc_c_n, rc_p_n):
    logger.info("**************************** start ****************************")
    logger.info(f"代码开始执行了{datetime.now()}")
    # 发送请求获取南航R舱报表数据
    rnum_excel_file = send_ycapi_request(rc_c_n)
    if rnum_excel_file is None:
        logger.info(f"获取南航R舱报表数据失败--->{rc_c_n}")
        return

    # ************************* 删除政策 *************************
    del_req = del_policy(rc_p_c)
    send_fzapi_request(del_req)
    logger.info(f"删除政策成功--->{rc_p_c}")
    time.sleep(30)

    # 合并数据，并且转化为飞猪报表
    if rnum_excel_file is not None:
        set_excel_table(rnum_excel_file, rc_p_c, rc_c_n, rc_p_n)
    else:
        logger.error(f"获取南航R舱报表数据失败")
        return False
    logger.info("**************************** end ****************************")
    return True


if __name__ == '__main__':
    # 初始化日志配置
    log_config()

    # merged_sheet_df = pd.read_excel('./XC/CZR-XC.xlsx', sheet_name="0520-0704")
    # fz_df = pd.read_excel('./XC/飞猪CZR-QN2.xlsx', sheet_name="结果")
    # 转飞猪报表
    # cz_excel_to_fz(merged_sheet_df, "YCHKCP-技术测试", "0520-0704", fz_df, "XC", "满满的世界")

    ipt_cmd = input("请输入指令:")
    for cmd in ipt_cmd.split(';'):
        c = cmd.split('/')
        logger.info(f"{cmd}开始执行... ...")
        flag = run_code(c[0], c[1], c[2])
        if flag:
            logger.info(f"{cmd}执行完成。")
        else:
            logger.info(f"{cmd}执行失败！！！")
